﻿#region Copyright (c) 2011, thinktecture (http://www.thinktecture.com)

/*
   thinktecture.UIAnnotations was developed by Jörg Neumann.
   Copyright (c) 2011, thinktecture (http://www.thinktecture.com).
   All rights reserved, comes as-is and without any warranty. Use of this
   source file is governed by the license which is contained in LICENSE.TXT 
   in the distribution.
*/

#endregion

using System;
using System.Linq;
using System.Reflection;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Windows;
using System.Windows.Data;
using System.Collections.Generic;

namespace Thinktecture.DataForm
{
    public static class AnnotationReader
    {
        private static TResult CheckLocalizedValue<TResult>(TResult value, object attribute)
        {
            if (value != null)
            {
                var resourceMgrProp = attribute.GetType().GetProperty("ResourceType");
                if (resourceMgrProp != null)
                {
                    var obj = resourceMgrProp.GetValue(attribute, new object[] { });
                    var resourceMgrType = resourceMgrProp.GetValue(attribute, new object[] { }) as Type;
                    if (resourceMgrType != null)
                    {
                        var localizerProp = resourceMgrType.GetProperty(value.ToString(), BindingFlags.NonPublic | BindingFlags.Static);
                        if (localizerProp != null)
                        {
                            var localizedValue = localizerProp.GetValue(null, new object[] { });
                            if (localizedValue != null)
                            {
                                value = (TResult)localizedValue;
                            }
                        }
                    }
                }
            }

            return value;
        }

        public static TResult GetAttributeValue<TEntityType, TAttributeType, TResult>(string propertyName, string attributeMember)
        {
            TypeDescriptor.AddProvider(
                new AssociatedMetadataTypeTypeDescriptionProvider(typeof(TEntityType)), typeof(TEntityType));
            PropertyDescriptor propDescriptor = TypeDescriptor.GetProperties(typeof(TEntityType)).Find(propertyName, true);
            foreach (object attrib in propDescriptor.Attributes)
            {
                if (typeof(TAttributeType).IsAssignableFrom(attrib.GetType()))
                {
                    PropertyInfo prop = attrib.GetType().GetProperty(attributeMember);
                    if (prop != null)
                    {
                        var value = (TResult)prop.GetValue(attrib, new object[] { });
                        value = CheckLocalizedValue(value, attrib);
                        return value;
                    }
                }
            }
            return default(TResult);
        }

        public static object GetValue(this BindingExpression expression, object dataItem)
        {
            if (expression == null || dataItem == null)
            {
                return null;
            }

            string bindingPath = expression.ParentBinding.Path.Path;
            string[] properties = bindingPath.Split('.');

            object currentObject = dataItem;
            Type currentType = null;

            for (int i = 0; i < properties.Length; i++)
            {
                currentType = currentObject.GetType();
                PropertyInfo property = currentType.GetProperty(properties[i]);
                if (property == null)
                {
                    currentObject = null;
                    break;
                }
                currentObject = property.GetValue(currentObject, null);
                if (currentObject == null)
                {
                    break;
                }
            }

            return currentObject;
        } 

        public static TResult GetAttributeValue<TAttributeType, TResult>(Type entityType, string propertyName, string attributeMember)
        {
            TypeDescriptor.AddProvider(
                new AssociatedMetadataTypeTypeDescriptionProvider(entityType), entityType);
            PropertyDescriptor propDescriptor = TypeDescriptor.GetProperties(entityType).Find(propertyName, true);
            if (propDescriptor != null)
            {
                foreach (object attrib in propDescriptor.Attributes)
                {
                    if (typeof(TAttributeType).IsAssignableFrom(attrib.GetType()))
                    {
                        PropertyInfo prop = attrib.GetType().GetProperty(attributeMember);
                        if (prop != null)
                        {
                            if (attributeMember.Equals("AutoGenerateField"))
                            {
                                var att = attrib as DisplayAttribute;
                                object generate = att.GetAutoGenerateField();
                                return (TResult)generate;
                            }
                            var value = (TResult)prop.GetValue(attrib, new object[] { });
                            value = CheckLocalizedValue(value, attrib);
                            return value;
                        }
                    }
                }
            }
            return default(TResult);
        }

        public static bool GetAttributeExists<TEntityType, TAttributeType>(string propertyName)
        {
            TypeDescriptor.AddProvider(
                new AssociatedMetadataTypeTypeDescriptionProvider(typeof(TEntityType)), typeof(TEntityType));
            PropertyDescriptor propDescriptor = TypeDescriptor.GetProperties(typeof(TEntityType)).Find(propertyName, true);
            foreach (object attrib in propDescriptor.Attributes)
            {
                if (attrib is TAttributeType)
                {
                    return true;
                }
            }
            return false;
        }

        public static bool GetAttributeExists<TAttributeType>(Type entityType, string propertyName)
        {
            TypeDescriptor.AddProvider(
                new AssociatedMetadataTypeTypeDescriptionProvider(entityType), entityType);
            PropertyDescriptor propDescriptor = TypeDescriptor.GetProperties(entityType).Find(propertyName, true);
            if (propDescriptor != null)
            {
                foreach (object attrib in propDescriptor.Attributes)
                {
                    if (attrib is TAttributeType)
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        public static DataTypeDescriptor GetTypeDescriptor(Type dataObjectType)
        {
            DataTypeDescriptor typeDescriptor = new DataTypeDescriptor(dataObjectType);
            PropertyInfo[] infos = dataObjectType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            foreach (var info in infos)
            {
                DataPropertyDescriptor propDescriptor = new DataPropertyDescriptor(info.Name, info.PropertyType);
                typeDescriptor.PropertyDescriptors.Add(propDescriptor);

                // Apply EditableAttribute
                bool? visible = AnnotationReader.GetAttributeValue<DisplayAttribute, bool?>(dataObjectType, info.Name, "AutoGenerateField");
                if (visible.HasValue)
                    propDescriptor.IsVisible = visible.Value;
                if (info.Name == "Item")
                    propDescriptor.IsVisible = false;

                // Association
                string associationName = AnnotationReader.GetAttributeValue<AssociationAttribute, string>(dataObjectType, info.Name, "Name");
                propDescriptor.AssociationName = associationName;
                string associationOtherKey = AnnotationReader.GetAttributeValue<AssociationAttribute, string>(dataObjectType, info.Name, "OtherKey");
                propDescriptor.AssociationOtherKey = associationOtherKey;
                string associationThisKey = AnnotationReader.GetAttributeValue<AssociationAttribute, string>(dataObjectType, info.Name, "ThisKey");
                propDescriptor.AssociationThisKey = associationThisKey;

                // Apply EditableAttribute
                bool? editable = AnnotationReader.GetAttributeValue<EditableAttribute, bool?>(dataObjectType, info.Name, "AllowEdit");
                propDescriptor.IsReadOnly = (editable.HasValue && editable.Value == false);

                // Apply RequiredAttribute
                bool required = AnnotationReader.GetAttributeExists<RequiredAttribute>(dataObjectType, info.Name);
                propDescriptor.IsRequired = AnnotationReader.GetAttributeExists<RequiredAttribute>(dataObjectType, info.Name);

                // Apply StringLength
                int? stringLength = AnnotationReader.GetAttributeValue<StringLengthAttribute, int?>(dataObjectType, info.Name, "MaximumLength");
                if (stringLength.HasValue)
                {
                    propDescriptor.StringLength = stringLength.Value;
                }

                // Apply DisplayFormat
                string formatString = AnnotationReader.GetAttributeValue<DisplayFormatAttribute, string>(dataObjectType, info.Name, "DataFormatString");
                if (!string.IsNullOrEmpty(formatString))
                {
                    propDescriptor.FormatString = formatString;
                }

                // Apply CueBanner
                string nullDisplayText = AnnotationReader.GetAttributeValue<DisplayFormatAttribute, string>(dataObjectType, info.Name, "NullDisplayText");
                if (!string.IsNullOrEmpty(nullDisplayText))
                {
                    propDescriptor.CueBannerText = nullDisplayText;
                }

                // Display name
                string displayName = AnnotationReader.GetAttributeValue<DisplayAttribute, string>(dataObjectType, info.Name, "Name");
                if (!string.IsNullOrEmpty(displayName))
                {
                    propDescriptor.DisplayName = displayName;
                }

                // Description
                string description = AnnotationReader.GetAttributeValue<DisplayAttribute, string>(dataObjectType, info.Name, "Description");
                if (!string.IsNullOrEmpty(description))
                {
                    propDescriptor.Description = description;
                }
            }
            return typeDescriptor;
        }

        public static DataTypeDescriptor FromElementBinding(UIElement element, DependencyProperty property)
        {
            if (element == null) throw new ArgumentNullException("element");
            if (property == null) throw new ArgumentNullException("property");
            if (BindingOperations.IsDataBound(element, property))
            {
                BindingExpression exp = BindingOperations.GetBindingExpression(element, property);
                object source = exp.DataItem;

                if (source != null && exp != null && exp.ParentBinding != null && exp.ParentBinding.Path != null && exp.ParentBinding.Path.Path != null)
                {
                    return new DataTypeDescriptor(source.GetType());
                }
            }
            return null;
        }

        public static List<BindingExpression> GetBindingExpressions(UIElement element)
        {
            if (element == null) throw new ArgumentNullException("element");
            var expressions = new List<BindingExpression>();
            FieldInfo[] properties = element.GetType().GetFields(BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Static | BindingFlags.FlattenHierarchy);
            foreach (FieldInfo field in properties)
            {
                if (field.FieldType == typeof(DependencyProperty))
                {
                    DependencyProperty dp = (DependencyProperty)field.GetValue(null);
                    if (BindingOperations.IsDataBound(element, dp))
                    {
                        BindingExpression exp = BindingOperations.GetBindingExpression(element, dp);
                        object source = exp.DataItem;

                        if (source != null && exp != null && exp.ParentBinding != null && exp.ParentBinding.Path != null && exp.ParentBinding.Path.Path != null)
                        {
                            expressions.Add(exp);
                        }
                    }
                }
            }
            return expressions;
        }

        public static void ChangeBindings(UIElement element, Action<DependencyProperty, BindingExpression> action)
        {
            if (element == null) throw new ArgumentNullException("element");
            var expressions = new List<BindingExpression>();
            FieldInfo[] properties = element.GetType().GetFields(BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Static | BindingFlags.FlattenHierarchy);
            foreach (FieldInfo field in properties)
            {
                if (field.FieldType == typeof(DependencyProperty))
                {
                    DependencyProperty dp = (DependencyProperty)field.GetValue(null);
                    if (BindingOperations.IsDataBound(element, dp))
                    {
                        BindingExpression exp = BindingOperations.GetBindingExpression(element, dp);
                        if (exp != null && exp.ParentBinding != null && exp.ParentBinding.Path != null && exp.ParentBinding.Path.Path != null)
                        {
                            action(dp, exp);
                        }
                    }
                }
            }
        }

        public static DataPropertyDescriptor FromElement(UIElement element, object source)
        {
            if (element == null) throw new ArgumentNullException("element");
            FieldInfo[] properties = element.GetType().GetFields(BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Static | BindingFlags.FlattenHierarchy);
            foreach (FieldInfo field in properties)
            {
                if (field.FieldType == typeof(DependencyProperty))
                {
                    DependencyProperty dp = (DependencyProperty)field.GetValue(null);
                    if (BindingOperations.IsDataBound(element, dp))
                    {
                        BindingExpression exp = BindingOperations.GetBindingExpression(element, dp);
                        //object source = exp.DataItem;

                        if (source != null &&
                            exp != null &&
                            exp.ParentBinding != null &&
                            exp.ParentBinding.Path != null &&
                            exp.ParentBinding.Path.Path != null &&
                            string.IsNullOrEmpty(exp.ParentBinding.ElementName))
                        {
                            var typeDescriptor = GetTypeDescriptor(source.GetType());
                            return typeDescriptor.PropertyDescriptors[exp.ParentBinding.Path.Path];
                        }
                    }
                }
            }
            return null;
        }
    }
}
